package com.labot.authentication.ldap.activedirectory;
 
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.labot.authentication.ldap.activedirectory.exception.ActiveDirectoryException;
import com.labot.authentication.ldap.dto.DtoUsuarioExchange;
import com.labot.service.UserManager;
import com.labot.types.ConstantsType;
import com.labot.util.StackTraceUtils;
import com.labot.webapp.listener.StartupListener;

/**
 * 
 * Clase encargada de la comunicacion con el DomainController/Active Directory
 * 
 * @author Enrique Barbeito
 *
 */
@Service(value = "domainController")
public class DomainController {
	
    @Autowired
    UserManager usuarioManager;

    public void setUserManager (UserManager manager) {
    	this.usuarioManager = manager;
    }

	//private static DomainController instancia = null;
	private static Hashtable<Object, String> ENV_DC = new Hashtable<Object, String>();
	private static LdapContext CTX_DC = null;
    private static DirContext [] LDAPS;  
	private static SearchControls SEARCH_CTL = null;
	private static final Log LOG = LogFactory.getLog(DomainController.class);
	private static String ldapthumbfield = "thumbnailPhoto";
	
	public void setConfiguration() {	
		//thumbnailPhoto

		String ldapHost 	= StartupListener.getConfigValue(ConstantsType.LDAP_HOST);
		String ldapPort 	= StartupListener.getConfigValue(ConstantsType.LDAP_PORT);
		String ldapUsername = StartupListener.getConfigValue(ConstantsType.LDAP_ADMINISTRATIVE_USER);
		String ldapPassword = StartupListener.getConfigValue(ConstantsType.LDAP_ADMINISTRATIVE_PASSWORD);
		String ldapDomain   = StartupListener.getConfigValue(ConstantsType.LDAP_DOMAIN);
		String ldapReferral = StartupListener.getConfigValue(ConstantsType.LDAP_REFERRAL);
		String ldapOU	    = StartupListener.getConfigValue(ConstantsType.LDAP_OU);
		ldapthumbfield	    = StartupListener.getConfigValue(ConstantsType.LDAP_PHOTO_ATTR_FIELD);
		
		LOG.info("------------------------------------------------------------------------------------------");
		LOG.info("LDAP: Initializing connection with ldap server.");
		LOG.info("LDAP: Server : " + "ldap://" + ldapHost + ":" + ldapPort); 
		LOG.info("LDAP: User   : " + ldapUsername + "@" + ldapDomain);
		LOG.info("------------------------------------------------------------------------------------------");
		LDAPS = new DirContext[1];
		ENV_DC.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		ENV_DC.put(Context.SECURITY_AUTHENTICATION, "simple");
		ENV_DC.put(Context.SECURITY_PRINCIPAL, ldapUsername + "@" + ldapDomain);
		ENV_DC.put(Context.SECURITY_CREDENTIALS, ldapPassword);
		ENV_DC.put(Context.PROVIDER_URL, "ldap://" + ldapHost + ":" + ldapPort);
		ENV_DC.put(Context.REFERRAL, ldapReferral);
		ENV_DC.put("OU", ldapOU);
		try {
			LDAPS[0] = new InitialDirContext(ENV_DC);
			CTX_DC = new InitialLdapContext(ENV_DC,null);
			SEARCH_CTL = new SearchControls();
			String[] attrs = new String[LdapAtt.values().length];
			int i = 0;
			for (LdapAtt ldapAtt : LdapAtt.values()) {
				if(ldapAtt.equals(LdapAtt.THUMBNAIL_PHOTO)){
					attrs[i] = ldapthumbfield;
				} else {					
					attrs[i] = ldapAtt.getValue();
				}
				i++;
			}
			SEARCH_CTL.setReturningAttributes(attrs);
			SEARCH_CTL.setSearchScope(SearchControls.SUBTREE_SCOPE);
			LOG.info("LDAP: Initialization ok.");
		} 
		catch (NamingException e) {
			LOG.error("Problem searching directory: " + StackTraceUtils.getStackTrace(e));
			//auditDao.insertAuditEvent(AuditType.ERROR_AL_CREAR_CONEXION_CON_LDAP, 0L, 0L, "Problema al inicializar LDAP: " + StackTraceUtils.getStackTrace(e));
		}

	}
	
	  /**
	   * This method authenticates the user by his key. The method searches for the DN
	   * of the user first, then uses that DN to authenticate.
	   *
	   * @param user - the user
	   *@param key is the key that used for seaching the user (uid as an example)
	   * @param pass - the password of the user
	   *@return a boolean value that indicates the authentication process. If it is false then the 
	   *authentication process fails. If it is true then the authentication process pass
	   */
	  public boolean authenticate(String key, String user, String pass) {
	      return authenticateUser(searchUser(key, user), pass);
	  }
	
	  /**
	  *	This function is used to authenticate a pair user/password 
	    *	against information stored in LDAP
	    *	
	    *	@param userDN is the user's DN
	    *	@param pass is the password of the above user
	    *	@return boolean value indicates whether or not user is successfully
	    *	authenticated in a specific LDAP server with the provided password.
	    *	
	    *	<p> Note that only simple authentication is supported at the moment.
	  */
	  
	public boolean authenticateUser(String userDN, String pass) {
	      setConfiguration();
	      
	      try{
	        if (userDN==null) 
	        	return false; 
	        if (pass==null) 
	        	return false;
	        if (pass.trim().equals("")) 
	        	return false;
	        Hashtable<Object, String> h = new Hashtable<Object, String>(ENV_DC);
	        h.put(Context.SECURITY_PRINCIPAL, userDN);
	        h.put(Context.SECURITY_CREDENTIALS, pass);
	        new InitialDirContext(h).close();
	        CTX_DC.close();
	        return true;
	      } catch (javax.naming.NamingException ne) {
	          return false;
	      }
	  }
	

	  /**
	  *	This function is used to search one user in the 
	  *	current LDAP server.
	  *	
	  *	@param userUID is the UID of the user in LDAP server
	   *@param key is a string that used to define the scope of the seach process
	  *	@return DN of the user if user with that UID is present
	  *	in LDAP server. Otherwise, return null.
	  */
	  
	  public String searchUser(String key, String user) {
		  setConfiguration();
	      
	      try {
	        SearchControls ctrl = new SearchControls();
	        ctrl.setSearchScope(SearchControls.SUBTREE_SCOPE);
	        String find = "(" + key+"=" + user +")";       
	        NamingEnumeration<?> answer = LDAPS[0].search("", find, ctrl);
	        String issuerDN = new String();
	        while (answer.hasMoreElements()) {
	            SearchResult result = (SearchResult) answer.next();
	            issuerDN = result.getName();
	            if (answer.hasMore()) {
	                return null;
	            }
	        }
	        String suffix=LDAPS[0].getNameInNamespace();
	        if (suffix.intern()!="") suffix = "," + suffix;
	        return issuerDN + suffix;
	      } catch (NamingException ne) {
	      }
	      try {
			CTX_DC.close();
		} catch (NamingException e) {
			e.printStackTrace();
		}
	      return null;
	  }  
	  
	 	  
	  
	  /**
	   * Devuelve un listado de DtoExchangeUser con todos los usuarios activos de la empresa
	   * @return
	   */
	  public List<DtoUsuarioExchange> getAll() {
		  return getFilterWithFilter("(&(objectClass=User)(mail=*))");
	  }  
	  
	  /**
	   * Devuelve un listado filtrado de DtoExchangeUser con todos los usuarios activos de la empresa
	   * @return
	   */
	  public List<DtoUsuarioExchange> getFilterAll(String user,String name) {
		  return getFilterWithFilter("(&(objectClass=User)(|(sAMAccountName="+user+")(name="+name+")))");
	  } 
	  
	  /**
	   * Devuelve un listado filtrado de DtoExchangeUser con todos los usuarios activos de la empresa
	   * @return
	   */
	  public List<DtoUsuarioExchange> getFilterWithFilter(String filter) {
		  setConfiguration();
		  String ldapOU	    = StartupListener.getConfigValue(ConstantsType.LDAP_OU); // Usuarios Activos (filtro)
	      
		  List<DtoUsuarioExchange> listado = new ArrayList<DtoUsuarioExchange>();
		  try {
		       String[] attrIDs = LdapAtt.getValuesArray();
		       SearchControls ctls = new SearchControls(); 
		       ctls.setReturningAttributes(attrIDs);
		       ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

		       NamingEnumeration<?> answer = LDAPS[0].search("", filter, ctls);
	
		       while (answer.hasMore()) {
		           SearchResult sr = (SearchResult)answer.next();
		           
		           if (sr.getName().contains(ldapOU)) { 
			           DtoUsuarioExchange usuario = new DtoUsuarioExchange();
			           usuario.setUsername(("" + sr.getAttributes().get(LdapAtt.S_AM_ACCOUNT_NAME.getValue())).replace("sAMAccountName: ", ""));
			           usuario.setName(getFullNameFromAttributes(sr.getName()));
			           usuario.setEmail(("" + sr.getAttributes().get(LdapAtt.MAIL.getValue())).replace("mail: ", ""));
			           usuario.setTelephone(("" + sr.getAttributes().get(LdapAtt.TELEPHONE_NUMBER.getValue())).replace("telephoneNumber: ", ""));			        	   
			           usuario.setDepartment(("" + sr.getAttributes().get(LdapAtt.DESCRIPTION.getValue())).replace("description: ", ""));
			           if (sr.getAttributes().get(ldapthumbfield) != null) {			        	   
			        	   usuario.setPhoto((byte[])sr.getAttributes().get(ldapthumbfield).get(0));
			           }
			           listado.add(usuario);
			           
		           }
		       }
		       CTX_DC.close();
		  } catch (Exception e) {
			  e.printStackTrace();
			  //auditDao.insertAuditEvent(AuditType.ERROR_LDAP, 0L, 0L, "Error al traer todos los usuarios: " + StackTraceUtils.getStackTrace(e));
		  }
	       
          return listado;
	  } 
	  
	  private String getFullNameFromAttributes (String linea) {
			// Ejemplo de linea "CN=Claudia Florencia Manzanel,OU=01- Activos Lab. Hidalgo,DC=labhidalgo,DC=net"
			Pattern p = Pattern.compile("CN=(.*?),OU=.*?");
			Matcher m = p.matcher(linea);
			if (m.find()) {
				return m.group(1);
			}
			return ""; 
	  }

	  
	  /**
	   * This method returns the LDAPRepository
	   *
	   *@return ldaps is the LDAP repository
	   */
	  public DirContext [] getLdaps() {
	      return LDAPS;
	  }
	  
	  public DtoUsuarioExchange getUser (String userName) throws ActiveDirectoryException {
		  setConfiguration();

		  List<DtoUsuarioExchange> usuarios = getFilterAll(userName, "");
		  
		  if((usuarios == null)||(usuarios.isEmpty())) {
			  throw new ActiveDirectoryException("User not found!");
		  }
		  if(usuarios.size() > 1) {
			  throw new ActiveDirectoryException("Assert ERROR, found <" + usuarios.size() + ">, but expected <1>");
		  }
		  
		  return usuarios.get(0);
	  }
	
}
